package com.coocaa.ops.api.base.aspect;

import com.coocaa.ops.api.base.cache.util.CacheTimeUtil;
import com.coocaa.ops.api.base.cache.annotation.Cache;
import com.coocaa.ops.api.base.cache.annotation.Category;
import com.coocaa.ops.api.base.cache.builder.AnnotationCacheMethodBuilder;
import com.coocaa.ops.api.base.cache.enums.InterfaceCacheMethodEnum;
import com.coocaa.ops.api.base.cache.model.SimpleCacheKey;
import com.coocaa.ops.api.base.cache.properties.CacheProperties;
import com.coocaa.ops.api.base.cache.service.SimpleCacheService;
import com.coocaa.ops.api.base.model.BaseQueryModel;
import com.coocaa.ops.api.common.validator.PolicyValidator;
import com.coocaa.ops.common.tool.aspect.AspectUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Objects;

/**
 * @author tangminghong
 * @date 2020-06-16
 */
@Component
@Aspect
@Slf4j
public class CacheAspect {

    @Autowired
    private CacheProperties cacheProperties;

    @Autowired
    private SimpleCacheService cacheService;

    /**
     * 对设置了@Cache注解的方法进行缓存处理
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */

    @Around("@annotation(com.coocaa.ops.api.base.cache.annotation.Cache)")
    public Object cacheAspect(ProceedingJoinPoint joinPoint) throws Throwable {
        if (notUseCache(joinPoint)) {
            return joinPoint.proceed();
        }
        //获取方法的@Cache注解
        Cache cacheInfo = this.getCacheAnnotation(joinPoint);
        //获取cacheMethod
        InterfaceCacheMethodEnum cacheMethod = cacheInfo.method();
        //
        SimpleCacheKey cacheKey = this.buildCacheKey(joinPoint, cacheMethod, cacheInfo.filterKey());
        //获取返回值类型
        Type returnType = AspectUtil.getReturnType(joinPoint);
        //获取缓存值
        Object resultData = cacheService.get(cacheKey, returnType);
        if (resultData != null) {
            return resultData;
        } else {
            log.info(String.format("ApiData从缓存中取值失败，键为:%s，将从数据库取值，请注意！", cacheKey.buildFinalKey()));
        }
        resultData = joinPoint.proceed();
        boolean needSetKey = isNeedSetKey(resultData, cacheKey);
        if (needSetKey) {
            //设置key-value, 针对测试策略值不做缓存处理
            cacheService.set(cacheKey, resultData, CacheTimeUtil.getFinalCacheTime(cacheInfo.expiration(), cacheInfo.discreteness()));
        }
        return resultData;
    }

    /**
     * 是否 需要设置缓存
     *
     * @param resultData
     * @param cacheKey
     * @return
     */
    private boolean isNeedSetKey(Object resultData, SimpleCacheKey cacheKey) {
        return null != resultData
                && cacheKey != null
                ;
    }


    /**
     * 是否不走缓存
     *
     * @param joinPoint
     * @return
     */
    private boolean notUseCache(ProceedingJoinPoint joinPoint) {
        if (!Objects.equals(cacheProperties.getEnableCache(), true)) {
            return true;
        }
        if (checkMethodReturnTypeIsVoid(joinPoint)) {
            return true;
        }
        //如果开启debug模式，就不走缓存了
        if (isDebug()) {
            return true;
        }
        //针对请求接口中 含有测试策略参数的 不走缓存逻辑
        Object[] args = joinPoint.getArgs();
        Annotation[][] annotations = ((MethodSignature)joinPoint.getSignature()).getMethod().getParameterAnnotations();
        int size = Math.min(args.length, annotations.length);
        for (int i = 0 ; i < size ; i++) {
            if (isTestPolicy(args[i], annotations[i])) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否debug
     *
     * @return
     */
    private boolean isDebug() {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        if (null == requestAttributes) {
            return false;
        }
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        String debugFlag = request != null ? request.getParameter("debug") : null;
        return "1".equals(debugFlag);
    }

    /**
     * 是否测试策略的
     *
     * @param obj
     * @return
     */
    private boolean isTestPolicy(Object obj, Annotation[] annotations) {
        if (obj == null) { return false; }
        if (obj instanceof BaseQueryModel
                && PolicyValidator.isTestPolicy(((BaseQueryModel) obj).getPolicyCategory())) {
            return true;
        }
        for (Annotation annotation : annotations) {
            if (annotation.annotationType().equals(Category.class)
                    && (obj instanceof Integer)
                    && PolicyValidator.isTestPolicy((Integer)obj)) {
                return true;
            }
        }
        return false;
    }


    private SimpleCacheKey buildCacheKey(ProceedingJoinPoint joinPoint, InterfaceCacheMethodEnum cacheMethod, String filterKey) {
        SimpleCacheKey cacheKey = SimpleCacheKey.getCacheKeyInstance(cacheMethod);

        Map<String, Object> methodParamMap = AnnotationCacheMethodBuilder.getMethodParamMap(joinPoint);

        AnnotationCacheMethodBuilder.buildCacheKeyFromParams(methodParamMap, filterKey, cacheKey);

        return cacheKey;
    }




    /**
     * 获取设置的缓存注解信息
     *
     * @param joinPoint
     * @return
     */
    private Cache getCacheAnnotation(ProceedingJoinPoint joinPoint) {
        Method method = AspectUtil.getMethod(joinPoint);
        return method.getAnnotation(Cache.class);
    }

    /**
     * 检查当前执行方法返回类型是否为void
     *
     * @param joinPoint
     * @return
     */
    private boolean checkMethodReturnTypeIsVoid(ProceedingJoinPoint joinPoint) {
        Type returnType = AspectUtil.getReturnType(joinPoint);
        //判断,返回类型是否为 void 的类型
        return "void".equalsIgnoreCase(returnType.toString());
    }


}
